package gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.rmi.RemoteException;
import java.util.HashMap;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;

import server.Cell;
import server.Server;

public class WorldViewPanel extends JPanel {

	private Server viewServer;
	private MouseListener controlListener;
	private Cell[] curCells;
	private HashMap<Cell, Ghost> curGhosts;
	private Cell selectedCell;
	private int selectedCritID;
	private boolean critSelected;
	
	private final Color defaultColor = new Color(128,220,255);
	private final Color plantColor = Color.green;
	private final Color rockColor = Color.black;
	private final Color foodColor = new Color(135, 58, 0);
	private final Color selectedRockColor = new Color(89, 31, 24);
	private final Color selectedPlantColor = new Color(16, 175, 12);
	private final Color selectedEmptyColor = new Color(60, 160, 230);
	
	/**The side length of a hexagon in pixels (half the width)*/
	public final int S = 30;
	/**Half the height of a hexagon in pixels*/
	public final int R = 26;
	/**One quarter of the width of a hexagon in pixels*/
	public final int H = S / 2;
	
	public final int MAX_ROW;
	public final int MAX_COL;
	public final int ROWS_PER_COL;
	public final int ROW_COL_EXPR;

	public Dimension getMinimumSize() {
		return new Dimension((H + S)*(MAX_COL + 1) + H, 2*R*(ROWS_PER_COL) + R + 1);
	}
	
	public Dimension getPreferredSize() {
		return getMinimumSize();
	}
	
	/**
	 * Returns the Cell that has most recently been clicked on.
	 */
	public Cell getSelectedCell() {
		return selectedCell;
	}
	
	
	public WorldViewPanel(Server vs) throws RemoteException{
		viewServer = vs;
		curCells = new Cell[0];
		curGhosts = new HashMap<Cell, Ghost>();
		MAX_ROW = viewServer.maxRow();
		MAX_COL = viewServer.maxColumn();
		ROWS_PER_COL = (2* MAX_ROW - MAX_COL)/2 + 1;
		ROW_COL_EXPR = (MAX_COL % 2 == 0) ? (2*(MAX_ROW+1) - (MAX_COL+1)) : (2*MAX_ROW - MAX_COL);
		addMouseListener(new CritterClickListener());
	}

	public void paintComponent(final Graphics g){
		Rectangle view = getVisibleRect();
		final int startCol = Math.max(view.x/(S+H) - 1, 0);
		final int endCol = Math.min((view.x + view.width)/(S+H), MAX_COL);
		final int startRow = Math.max(view.y/(2*R), 1);
		final int endRow = Math.min((view.y + view.height)/(2*R) + 1, ROWS_PER_COL);

		SwingWorker<HashMap<Cell, Ghost>, Object> retriever = new SwingWorker<HashMap<Cell, Ghost>, Object>(){
			protected HashMap<Cell, Ghost> doInBackground() throws Exception {
				Cell[] cells = viewServer.getSubsection(startCol, startRow, endCol - startCol, endRow - startRow);
				HashMap<Cell, Ghost> rtn = new HashMap<Cell, Ghost>();
				for(Cell c : cells){
					if(c.hasCritter()){
						int[] critMem = viewServer.getCritterMemory(c.critId());
						Ghost g = new Ghost(critMem);
						rtn.put(c, g);
					}
				}
				return rtn;
			}
			protected void done(){
				try{
					HashMap<Cell, Ghost> info = get();
					Cell[] cells = info.keySet().toArray(new Cell[0]);
					synchronized(curCells){curCells = cells;}
					synchronized(curGhosts){curGhosts = info;}
				} catch (Exception e) {
					JOptionPane.showMessageDialog(SwingUtilities.getRootPane(WorldViewPanel.this),
							e.getMessage() + "\n - world not updated", "Error", JOptionPane.ERROR_MESSAGE);
				}
			}
		};
		retriever.execute();
		
		g.setColor(Color.GRAY);
		g.fillRect(0, 0, getWidth(), getHeight());
		g.setColor(Color.BLACK);
		//Draw base of every cell
		for(int c = startCol; c <= endCol; c++){
			for(int r = endRow; r >= startRow; r--){//relative row
				if(c % 2 == 0)
					drawOneHex(g, defaultColor, (c*(S+H) + H), r*2*R + R);
				else
					drawOneHex(g, defaultColor, (c*(S+H) + H), r*2*R);
			}
		}
		//Draw contents of every cell
		boolean critUpdated = !critSelected;
		for(Cell c : curCells){
			if(critSelected && c.hasCritter() && c.critId() == selectedCritID){
				selectedCell = c;
				critUpdated = true;
			}
			if(c.col() % 2 == 0)
				drawOneHex(g, c, c.col()*(S+H) + H, 
						(ROWS_PER_COL + (c.col() + 1)/2 - c.row())*2*R + R);
			else
				drawOneHex(g, c, c.col()*(S+H) + H, 
						(ROWS_PER_COL + (c.col() + 1)/2 - c.row())*2*R);
		}
		if(!critUpdated){
			critSelected = false;
			selectedCell = null;
			if(controlListener != null)
				controlListener.mouseClicked(new MouseEvent(WorldViewPanel.this, 0, 0, 0, 0, 0, 0, false));
		}
		if (selectedCell != null) {
			drawSelectedHex(g);
		}
	}
	
	private void drawSelectedHex(Graphics g) {
		int c = selectedCell.col();
		int startX = (c*(S+H) + H);
		int row = selectedCell.row();
		int r = ROWS_PER_COL - row + (c+1)/2;
		int startY;
		if (c % 2 == 0) startY = r*2*R + R;
		else startY = r*2*R;
		drawOneHex(g, Color.WHITE, startX, startY);
		if (selectedCell.hasRock()) {
			drawOneHex(g, selectedRockColor, startX, startY);
		} else if (selectedCell.hasPlant()) {
			drawOneHex(g, selectedPlantColor, startX, startY);
		} else {
			drawOneHex(g, selectedEmptyColor, startX, startY);
		}
		if (selectedCell.hasDead())
			drawFood(g, foodColor, startX, startY);
		if (selectedCell.hasCritter()){
			drawCritter(g, selectedCell, startX, startY);
		}
	}
	
	/**
	 * Draws one Hex, starting with the bottom-left corner, and fills it with a Critter
	 * or a rock or a plant etc. (if needed)
	 */
	private void drawOneHex(Graphics g, Cell cellToDraw, int startX, int startY){
		if(cellToDraw.hasRock())
			drawOneHex(g, rockColor, startX, startY);
		else{
			if (cellToDraw.hasPlant())
				drawOneHex(g, plantColor, startX, startY);
			if(cellToDraw.hasDead())
				drawFood(g, foodColor, startX, startY);
			if(cellToDraw.hasCritter()){
				drawCritter(g, cellToDraw, startX, startY);
			}
		}
	}

	/**
	 * Draws one complete hexagon, starting with the bottom-left corner
	 */
	private void drawOneHex(Graphics g, Color fillColor, int startX, int startY){
		int[] xPoints = new int[6];
		xPoints[0] = startX;
		xPoints[1] = xPoints[0] + S;
		xPoints[2] = xPoints[1] + H;
		xPoints[3] = xPoints[2] - H;
		xPoints[4] = xPoints[3] - S;
		xPoints[5] = xPoints[4] - H;

		int[] yPoints = new int[6];
		yPoints[0] = startY;
		yPoints[1] = yPoints[0];
		yPoints[2] = yPoints[1] - R;
		yPoints[3] = yPoints[2] - R;
		yPoints[4] = yPoints[3];
		yPoints[5] = yPoints[4] + R;
		
		g.setColor(fillColor);
		g.fillPolygon(xPoints, yPoints, 6);
		
		g.setColor(Color.black);
		g.drawPolygon(xPoints, yPoints, 6);
	}
	
	private void drawFood(Graphics g, Color fillColor, int startX, int startY){
		g.setColor(fillColor);
		g.fillOval(startX - H/4, startY - R/2, H/3, H/2);
		g.fillOval(startX + H, startY - H, H/3, R/2);
		g.fillOval(startX + 7*S/8, startY - 8*H/7, H/3, H/4);
		g.fillOval(startX - H/2, startY - 4*R/3, H, S/3);
		g.fillOval(startX + S/3, startY - 7*R/6, H/2, H/2);
		g.fillOval(startX + 5*S/6, startY - 4*R/3, H, H);
		g.fillOval(startX, startY - 15*R/8, 3*S/5, 2*H/3);
	}

	private void drawCritter(final Graphics g, final Cell cell, final int startX, final int startY) {
		Ghost ghost;
		synchronized(curGhosts){ghost = curGhosts.get(cell);}
		int[] critMem = ghost.getMem();
		int centerX = startX + H;
		int centerY = startY - R;
		int sizeFactor = Math.min(critMem[3], 10);
		int legLength = (3*H/2 + (H/5)*(sizeFactor - 1))/2;
		int circleWidth = 4*legLength/3;

		g.setColor(Color.black);
		g.drawLine(centerX - legLength, centerY, centerX + legLength, centerY);
		g.drawLine(centerX - legLength/2, centerY - 6*legLength/7, centerX + legLength/2, centerY + 6*legLength/7);
		g.drawLine(centerX + legLength/2, centerY - 6*legLength/7, centerX - legLength/2, centerY + 6*legLength/7);

		float redVal = Math.min(1f, critMem[2] / 15f);
		float blueVal = Math.min(1f, critMem[1] / 15f);
		float greenVal = ((float) critMem[4]) / (critMem[3] * 500);

		g.setColor(new Color(redVal, greenVal, blueVal));
		g.fillOval(centerX - circleWidth/2, centerY - circleWidth/2, circleWidth, circleWidth);

		g.setColor(Color.black);
		int headWidth = circleWidth/3;
		int headCenterX;
		int headCenterY;
		switch(cell.critDirection()){
		case 1:
			headCenterX = centerX + circleWidth/2;
			headCenterY = centerY - circleWidth/4;
			break;
		case 2:
			headCenterX = centerX + circleWidth/2;
			headCenterY = centerY + circleWidth/4;
			break;
		case 3:
			headCenterX = centerX;
			headCenterY = centerY + 7*circleWidth/12;
			break;
		case 4:
			headCenterX = centerX - circleWidth/2;
			headCenterY = centerY + circleWidth/4;
			break;
		case 5:
			headCenterX = centerX - circleWidth/2;
			headCenterY = centerY - circleWidth/4;
			break;
		default:
			headCenterX = centerX;
			headCenterY = centerY - 7*circleWidth/12;
			break;
		}
		g.fillOval(headCenterX - headWidth/2, headCenterY - headWidth/2, headWidth, headWidth);
	}
	
	/**
	 * Translates the given x and y pixel coordinates into a coordinate in the CritterWorld
	 * @param x x pixel coordinate
	 * @param y y pixel coordinate
	 * @return the row, column coordinate corresponding to the given coordinates
	 */
	public int[] getCoordinatesFromPixels(int x, int y){
		int secx = x / (S + H);
		int secy = y / (2*R);
		int relx = x - secx*(S + H);
		int rely = y - secy*2*R;
		
		int topRow; 
		int hexType;
		int[] rtn = new int[2];
		if(secx % 2 == 0){
			hexType = getEvenType(relx, rely);
			switch(hexType){
			case 1:
				topRow = (secx + ROW_COL_EXPR) / 2;
				rtn[0] = topRow - secy + 1;
				rtn[1] = secx;
				break;
			case 2:
				topRow = (secx + ROW_COL_EXPR) / 2;
				rtn[0] = topRow - secy;
				rtn[1] = secx;
				break;
			default:
				topRow = (secx - 1 + ROW_COL_EXPR) / 2;
				rtn[0] = topRow - secy;
				rtn[1] = secx - 1;
				break;
			}
		} else {
			hexType = getOddType(relx, rely);
			switch(hexType){
			case 1:
				topRow = (secx + ROW_COL_EXPR) / 2;
				rtn[0] = topRow - secy;
				rtn[1] = secx;
				break;
			case 2:
				topRow = (secx - 1 + ROW_COL_EXPR) / 2;
				rtn[0] = topRow - secy + 1;
				rtn[1] = secx - 1;
				break;
			default:
				topRow = (secx - 1 + ROW_COL_EXPR) / 2;
				rtn[0] = topRow - secy;
				rtn[1] = secx - 1;
				break;
			}
		}
		return rtn;
	}
	
	/**
	 * Returns the hex type in an even column section based on the given
	 * relative x and y pixel coordinates
	 */
	private int getEvenType(int relx, int rely){
		int type;
		if (rely < R)
			type = (relx > rely / (((double)R)/H)) ? 1 : 3;
		else
			type = (relx > 2*H - rely / (((double)R)/H)) ? 2 : 3;
		return type;
	}
	
	/**
	 * Returns the hex type in an odd column section based on the given
	 * relative x and y pixel coordinates
	 */
	private int getOddType(int relx, int rely){
		int type;
		if (rely < R)
			type = (relx > H - rely / (((double)R)/H)) ? 1 : 2;
		else
			type = (relx > rely / (((double)R)/H) - H) ? 1 : 3;
		return type;
	}
	
	public void addControlMouseListener(MouseListener ml){
		controlListener = ml;
	}
	
	/**
	 * Returns an empty cell that does not actually exist in the world.
	 * If the coordinates are invalid for the world displayed by this panel,
	 * it will return null.
	 */
	public Cell newEmptyCell(int row, int col){
		// Checks if this Location if off the East Side.
		if (col > MAX_COL){
			return null;
		}
		// Checks if this Location if off the West Side.
		if (col < 0){
			return null;
		}
		// Checks if this Location if off the South Side.
		if (col > 2*row){
			return null;
		}
		// Checks if this Location if off the North Side.
		if (col + ROW_COL_EXPR < 2*row){
			return null;
		}
		return new Cell(row, col, false, false, false, false, -1, -1, -1, -1, -1);
	}

	private class CritterClickListener extends MouseAdapter{
		public void mousePressed(final MouseEvent e){
			final int[] rowCol = getCoordinatesFromPixels(e.getX(), e.getY());
			SwingWorker<Cell[], Object> updater = new SwingWorker<Cell[], Object>(){
				protected Cell[] doInBackground() throws Exception {
					return viewServer.getSubsection(rowCol[1], rowCol[0], 1, 1);
				}
				protected void done(){
					try {
						Cell[] cellArr = get();
						if(cellArr.length == 0){
							selectedCell = newEmptyCell(rowCol[0], rowCol[1]);
							critSelected = false;
						}
						else{
							selectedCell = cellArr[0];
							if(selectedCell.hasCritter()){
								critSelected = true;
								selectedCritID = selectedCell.critId();
							}
							else
								critSelected = false;
						}
						if(controlListener != null)
							controlListener.mouseClicked(e);
					} catch (Exception e1) {
						selectedCell = null;
						critSelected = false;
					}
					SwingUtilities.getRootPane(getParent()).repaint();
				}
			};
			updater.execute();
		}
	}
}
